﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Flower.Client;
using Flower.Directory.Host.Default;
using Flower.Directory.Util;
using Flower.Services;
using Flower.Services.Data;
using NUnit.Framework;
using Flower.Testing;
using System.Transactions;
using System.Threading;

namespace Flower.StateService.Tests
{
    [TestFixture]
    public class StateServiceTests
    {
        #region MockStateStorage

        private class MockStateStorage : IStateStorage
        {
            private readonly List<StateInfo> _states = new List<StateInfo>(); 

            public void Save(StateInfo state)
            {
                lock (_states)
                {
                    _states.Add(state);
                }
            }

            public void Delete(Guid stateId)
            {
                lock (_states)
                {
                    _states.RemoveAll(s => s.Id == stateId);
                }
            }

            public StateInfo GetLatestState(string pid)
            {
                lock (_states)
                {
                    var procStates =
                        _states
                            .Where(s => s.Pid == pid)
                            .ToList();

                    DateTime maxTimestamp = DateTime.MinValue;
                    StateInfo latestState = null;

                    foreach (StateInfo procState in procStates)
                    {
                        if (procState.ArrivedOn > maxTimestamp)
                        {
                            latestState = procState;
                            maxTimestamp = procState.ArrivedOn;
                        }
                    }

                    return latestState;
                }
            }

            public IEnumerable<StateInfo> GetAllStates()
            {
                lock (_states)
                {
                    return _states;
                }
            }

            public void UpdateFlags(Guid stateId, StateFlags flags)
            {
                lock (_states)
                {
                    _states.First(s => s.Id == stateId).Flags = flags;
                }
            }
        }

        #endregion

        #region Directory

        private static IDirectory MakeDirectroy(out string pid)
        {
            return
                new DirectoryBuilder(new MemoryDirectory.Directory(new ScriptEnginePool(10)))
                    .RegisterProcessor("TestProcessor")
                    .Root("/Processors/TestProcessor/Running")
                        .Process("WF", out pid).End()
                    .End()
                    .Build();
        }

        private static IDirectory MakeDirectroy(out string pid1, out string pid2)
        {
            return
                new DirectoryBuilder(new MemoryDirectory.Directory(new ScriptEnginePool(10)))
                    .RegisterProcessor("TestProcessor")
                    .Root("/Processors/TestProcessor/Running")
                        .Process("WF", out pid1).End()
                        .Process("WF", out pid2).End()
                    .End()
                    .Build();
        }

        #endregion

        [Test]
        public void TransactionalSave()
        {
            string pid;

            using(var client = new FlowerClient(MakeDirectroy(out pid)))
            {
                var storage = new MockStateStorage();
                var stateService = new StateService(client, storage, new StateServiceSettings(), true);

                using (var trn = new TransactionScope())
                {
                    stateService.Save
                    (
                        pid,
                        1.ToBlob(BlobFormat.BinXml), 
                        true
                    );
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsNull(stateService.GetLatestState(pid));

                using (var trn = new TransactionScope())
                {
                    stateService.Save
                    (
                        pid,
                        1.ToBlob(BlobFormat.BinXml),
                        true
                    );

                    trn.Complete();
                }

                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsNotNull(stateService.GetLatestState(pid));
            }
        }

        [Test]
        public void StateSavingAndPropagation()
        {
            string pid;

            using (var client = new FlowerClient(MakeDirectroy(out pid)))
            {
                var storage = new MockStateStorage();
                var stateService = new StateService(client, storage, new StateServiceSettings(), true);

                Guid stateId = stateService.Save
                (
                    pid,
                    1.ToBlob(BlobFormat.BinXml),
                    true
                );

                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsTrue(storage.GetAllStates().Any(s => s.Id == stateId));
                Assert.IsTrue(client.Directory.Exists("/Processors/TestProcessor/Running/" + pid + "/" + stateId, DateTime.MinValue));
                
                stateId = stateService.Save
                (
                    pid,
                    1.ToBlob(BlobFormat.BinXml),
                    false
                );

                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsTrue(storage.GetAllStates().Any(s => s.Id == stateId));
                Assert.IsFalse(client.Directory.Exists("/Processors/TestProcessor/Running/" + pid + "/" + stateId, DateTime.MinValue));
                
                Guid prevStateId = stateId;

                stateId = stateService.Save
                (
                    pid,
                    1.ToBlob(BlobFormat.BinXml),
                    false
                );
                
                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsTrue(storage.GetAllStates().Any(s => s.Id == stateId));
                Assert.IsTrue(storage.GetAllStates().All(s => s.Id != prevStateId));
            }
        }

        [Test]
        public void StateMarks()
        {
            string pid;

            using (var client = new FlowerClient(MakeDirectroy(out pid)))
            {
                var storage = new MockStateStorage();
                var stateService = new StateService(client, storage, new StateServiceSettings(), true);

                const int lim = 10;

                for (int i = 0; i < lim; i++)
                {
                    stateService.Save
                    (
                        pid,
                        1.ToBlob(BlobFormat.BinXml),
                        false
                    );

                    if (i % 2 == 0)
                    {
                        stateService.MarkLatestStateAsKeyState(pid);
                    }
                }

                Thread.Sleep(TimeSpan.FromSeconds(5));

                Assert.AreEqual(1, storage.GetAllStates().Count());
                Assert.AreEqual(lim / 2, client.Directory.CountChildren("/Processors/TestProcessor/Running/" + pid, DirectoryEntryTypes.State));

                Guid stateId = stateService.Save
                (
                    pid,
                    1.ToBlob(BlobFormat.BinXml),
                    false
                );

                stateService.MarkLatestStateAsFinalState(pid);

                Thread.Sleep(TimeSpan.FromSeconds(1));

                Assert.IsNull(stateService.GetLatestState(pid));
                Assert.IsTrue(client.Directory.Exists("/Processors/TestProcessor/Running/" + pid + "/" + stateId, DateTime.MinValue));
            }
        }

        [Test]
        public void GetLatestState()
        {
            string pid1, pid2;

            using (var client = new FlowerClient(MakeDirectroy(out pid1, out pid2)))
            {
                var storage = new MockStateStorage();
                var stateService = new StateService(client, storage, new StateServiceSettings(), true);

                for (int i = 0; i < 2; i++)
                {
                    Guid stateId1 = stateService.Save
                    (
                        pid1,
                        1.ToBlob(BlobFormat.BinXml),
                        false
                    );
                    Guid stateId2 = stateService.Save
                    (
                        pid2,
                        1.ToBlob(BlobFormat.BinXml),
                        false
                    );

                    Assert.AreNotEqual(stateId1, stateId2);

                    var latestState1 = stateService.GetLatestState(pid1);
                    var latestState2 = stateService.GetLatestState(pid2);

                    Assert.NotNull(latestState1);
                    Assert.NotNull(latestState2);
                    Assert.IsTrue(latestState1.Id == stateId1);
                    Assert.IsTrue(latestState2.Id == stateId2);
                }
            }
        }

        [Test]
        public void StateTimeout()
        {
            string pid;

            using (var client = new FlowerClient(MakeDirectroy(out pid)))
            {
                var storage = new MockStateStorage();
                var stateService = 
                    new StateService
                    (
                        client, 
                        storage, 
                        new StateServiceSettings { StateTimeout = TimeSpan.FromSeconds(1) }
                    );

                Guid stateId = stateService.Save
                (
                    pid,
                    1.ToBlob(BlobFormat.BinXml),
                    false
                );

                Thread.Sleep(TimeSpan.FromSeconds(10));

                Assert.IsNull(stateService.GetLatestState(pid));
                Assert.IsTrue(client.Directory.Exists("/Processors/TestProcessor/Running/" + pid + "/" + stateId, DateTime.MinValue));
            }
        }

        [Test]
        public void StartStop()
        {
            string pid1, pid2;

            using (var client = new FlowerClient(MakeDirectroy(out pid1, out pid2)))
            {
                var storage = new MockStateStorage();
                var stateService = new StateService(client, storage, new StateServiceSettings(), true);

                Guid stateId1 = Guid.Empty, stateId2 = Guid.Empty;

                for (int i = 0; i < 2; i++)
                {
                    stateId1 = stateService.Save
                    (
                        pid1,
                        1.ToBlob(BlobFormat.BinXml),
                        false
                    );
                    stateId2 = stateService.Save
                    (
                        pid2,
                        1.ToBlob(BlobFormat.BinXml),
                        false
                    );
                }

                stateService.Dispose();

                Assert.AreEqual(2, storage.GetAllStates().Count());

                stateService = new StateService(client, storage, new StateServiceSettings(), true);

                var latestState1 = stateService.GetLatestState(pid1);
                var latestState2 = stateService.GetLatestState(pid2);

                Assert.NotNull(latestState1);
                Assert.NotNull(latestState2);
                Assert.IsTrue(latestState1.Id == stateId1);
                Assert.IsTrue(latestState2.Id == stateId2);
            }
        }
    }
}
